#!/usr/bin/env python

import sys
import socket
import time
import re
import random
from threading import Thread, Timer, Event, Lock
from optparse  import OptionParser

LOTH = 5 
HITH = 10

class msg_server(Thread):
	
	def __init__(self, handler, port=3721):
		Thread.__init__(self)
		sock = socket.socket(
				socket.AF_INET, socket.SOCK_DGRAM)
		self.sock = sock
		sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
		self.port = port
		self.handler = handler

		try:
			sock.bind(('',port))
		except:
			sys.stderr.write("Can't bind to %d\n" % port)
			sys.exit(-1)
		sys.stdout.write("msg server listening on port %d\n"% port)
	

	def run(self):
		while 1:
			try:
				data, addr = self.sock.recvfrom(1024)
#				print 'Got data from %s:%s' %  (addr[0],addr[1])
				self.handler.process_msg(data)
			except socket.error, e:
				print "Recv Error"	
class msg_client:
	def __init__(self):
		sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
		self.sock = sock
		self.queue  = []
		self.timer = None
		self.qlock = Lock()

	def timerout(self):
		if len(self.queue)==0:
			return
		e = self.queue.pop(-1)
		self.real_send(e[0],e[1])	
		if self.queue !=[]:
			self.timer = Timer(self.queue[-1][3],self.timerout)
			self.timer.start()
		self.ct = time.time()	

	def send(self, msg, addr):

		# randomize the sending, creating a non-FIFO scenario

		rand_delay = random.randint(1, 3)
		ct = time.time()
#		print "delay:", rand_delay
#		print self.queue
		self.qlock.acquire()
		if self.queue == []:
			self.queue.append((msg,addr,ct,rand_delay))
			ring = rand_delay
			self.timer = Timer(ring, self.timerout)
			self.timer.start()
			self.ct = time.time()
		else:
			elapsed_time = ct-self.ct
#			print "elapse: ", elapsed_time
			if elapsed_time+rand_delay<self.queue[-1][3]:
				self.queue.append((msg, addr, ct, rand_delay))

#					self.queue[-2][3] -= elapsed_time+rand_delay
				e = self.queue.pop(-2)
				self.queue.insert(-1, (e[0],e[1],e[2],e[3]-(elapsed_time+rand_delay)))

				self.timer.cancel()
				ring = rand_delay
				self.timer = Timer(ring, self.timerout)
				self.timer.start()
				self.ct = time.time()
			elif len(self.queue)==1:
				self.queue.insert(-1, (msg, addr, ct, elapsed_time + rand_delay - self.queue[-1][3]))
			else:
				j = -1
				t_pre = self.queue[j][3]
				t = self.queue[j][3]+self.queue[j-1][3]
				while elapsed_time + rand_delay > t and j-1>0-len(self.queue):
					j -= 1
					t_pre = t
					t += self.queue[j-1][3]
				if j-1 == 0-len(self.queue) and elapsed_time + rand_delay < t:
					j = 0-len(self.queue)

				self.queue.insert(j, (msg, addr, ct, elapsed_time + rand_delay - t_pre))
#				self.queue[j-2][3] = t - elapsed_time + rand_delay
				if j!=0-len(self.queue)+1:
					e = self.queue.pop(j-2)
					self.queue.insert(j-1, (e[0],e[1],e[2],t-(elapsed_time+rand_delay)))
		self.qlock.release()			


	def real_send(self, msg, addr):
		if msg!='':
			try:
				self.sock.sendto(msg, addr)
			except socket.error, e:
				sys.stderr.write("sending error\n")
		else:
			print "empty message!"
			

class file_server:
	def __init__(self, nodes, file='vector_fs.log'):
		self.clients = nodes[1:]
		self.server = nodes[0]
		self.msg_srv = msg_server(handler = self, port = self.server[1])
		self.msg_clt = msg_client()
		self.msg_srv.start()
		self.processed = [0]*len(self.clients)
		self.queue = []
		self.file = file
		f = open(self.file, 'w')
		f.close()
		self.state = 'IDLE'
	
	def process_msg(self, msg):
		if re.match('^(.+):(.+)$',msg):
			q = re.match('^(.+):(.+)$',msg)
			cmd = q.group(1)
			content = q.group(2)
		if cmd == "REQ":
			vector = re.split(',',content)
			id = int(vector[0])
			print "REQUEST from link %s :" % vector[0],
			vector = map(int,vector[1:])
			print vector
			self.on_request(id, vector)

		elif cmd == "WRT":
			q = re.split(',', content)
			self.on_operate (int(q[0]),q[1])
			print "OPERATION from link %s" % q[0]
			
		elif cmd == "RLS":
			id = int(content)
			print "RELEASE from link %d" % id
			self.on_release(id)
		else:
			print "Message Error"

	def on_operate(self, id, content):
		time_vector = '%d' % self.granted_time[0] 
		for i in self.granted_time[1:]:
			time_vector += " %d" % i
		f=open(self.file, 'a')
		f.write(content+': '+time_vector+'\n')
		f.close()
		self.on_operate_finish(id)

	def on_operate_finish(self, id):
		
		self.msg_clt.send('FIN:%d' % id ,self.clients[id - 1])

	def on_request(self, id, vector):
		if self.state == 'IDLE':
			if self.is_next(id, vector):
				self.grant(id, vector)
				self.processed[id-1] += 1
			else:
				self.queue.append((id, vector))
		else:
			self.queue.append((id, vector))

	def is_next(self, id, vector):
		waiting = self.processed[:]
		waiting[id-1] += 1

		result = True
		if vector[id-1] != waiting[id - 1]:
			return False
		else:
			for i in range(len(vector)):
				if i == id -1:
					continue
				if vector[i]>waiting[i]:
					result=False
					break
		return result	

	def grant(self,id,vector):
		self.msg_clt.send('GRT:%d' % id, self.clients[id - 1] )
		print "Grant to %d," % id ,
		print vector
		self.granted_time = vector[:]
		self.state = 'BUSY'

	def on_release(self, id):
		print "Queue Status:", self.queue
		self.state = 'IDLE'
		for req in self.queue:
			id = req[0]
			vector = req[1]
			if self.is_next(id, vector):
				self.processed[id-1] += 1
				self.queue.remove(req)
				self.grant(id,vector)
				break



class file_client:
	def __init__(self, nodes, id):
		self.clients = nodes[1:]
		self.server = nodes[0]
		self.id = id
		self.msg_srv = msg_server(handler = self, port = self.clients[id-1][1])
		self.msg_clt = msg_client()
		self.msg_srv.start()
		self.time = [0]*len(self.clients)

	def process_msg(self, msg):
		if re.match('^(.+):(.+)$',msg):
			q = re.match('^(.+):(.+)$',msg)
			cmd = q.group(1)
			content = q.group(2)
		else:
			print "Message Error"

		if cmd =="EVT":
			vector = re.split(',',content)
			id = int(vector[0])
			print "EVENT from link %s :" % vector[0],
			vector = map(int,vector[1:])
			print vector
			self.on_event(id, vector)

		elif cmd == "GRT":
			self.on_granted()
			
		elif cmd == "FIN":
			self.on_operate_finish()
		else:
			print "Message Error"
	def on_granted(self):
		self.msg_clt.send("WRT:%d,Node %d is here\n" % (self.id, self.id), self.server)

	def on_operate_finish(self):
		self.release()

	def release(self):
		self.msg_clt.send("RLS:%d" % self.id, self.server)

	def on_event(self, id, vector):
		print self.time,'+',
		for i in range(len(vector)):
			self.time[i] = max(self.time[i], vector[i])
		print vector, '=', self.time	

	def send_event(self, id):
		nb = self.clients[id-1]
		msg = "EVT:%d" % self.id
		for i in self.time:
			msg += ",%d" % i
		self.msg_clt.send(msg, nb)

	def send_request(self):
		self.time[self.id - 1] += 1
		msg = "REQ:%d" % self.id
		print 'SEND REQ, Timer Update to: ',
		print self.time
		for i in self.time:
			msg += ",%d" % i
		self.msg_clt.send(msg, self.server)

	def evt_timeout_handler(self):
		rnd_id = random.randint(1, len(self.clients))
		if rnd_id != self.id:
			self.send_event(rnd_id)
		self.evt_timer = Timer(random.randint(LOTH, HITH), self.evt_timeout_handler)
		self.evt_timer.start()

	def req_timeout_handler(self):
		self.send_request()
		self.req_timer = Timer(random.randint(LOTH, HITH), self.req_timeout_handler)
		self.req_timer.start()


	def run(self):
		self.evt_timer = Timer(random.randint(LOTH, HITH), self.evt_timeout_handler)
		self.req_timer = Timer(random.randint(LOTH, HITH), self.req_timeout_handler)
		self.evt_timer.start()
		self.req_timer.start()

if __name__=='__main__':
	
	parser = OptionParser()
	parser.add_option("-f", "--file", 
			  dest="filename", default = "vector_fs.log",
	                  help="file name on the server")
	parser.add_option("-i", "--id",
	                  dest="id", default=0, type = "int",
			  help="process id, 0 for server")
	parser.add_option("-c", "--config", type = "string",
			  dest="config_file", default = "vector_fs.config",
			  help="config file name")

	(options, args) = parser.parse_args()

	nodes=[]
	f=open(options.config_file)
	for l in f.readlines():
		q = re.split('\s+',re.split('\n',l)[0])
		node = (q[1],int(q[2]))
		nodes.append(node)
	if options.id==0:
		proc = file_server(nodes, options.filename)
	else:
		proc = file_client(nodes, options.id)
		proc.run()

#	proc.run()	

